#include "UtilityParsers.hpp"

static int digitValue(char c, int base)
{
	int val = 0;
	if(c >= '0' && c <= '9')
	{
		val = c - '0';
	}
	else if(c >= 'a' && c <= 'z')
	{
		val = 10 + (c - 'a');
	}
	else if(c >= 'A' && c <= 'Z')
	{
		val = 10 + (c - 'A');
	}
	else
	{
		val = -1;
	}

	return val < base ? val : -1;
}

SourceLocation StringParser::parse(InputStream * stream)
{
	static char const escapeTable[] = "a\ab\bf\fn\nr\rt\t\\\\\'\'\"\"";

	enum State { Initial, Normal, Slash, Hex, Finished };

	size_t digVal = 0;
	int base = 0;
	char quoteChar = 0;
	
	SourcePosition escStart;

	for(State state = Initial; state != Finished; )
	{
		InputGetter g(stream);

		if(state == Initial)
		{
			if(g.eof())
			{
				ParsingErrorException ex(
					stream->cursorLocation(),
					"Unexpected end of file while looking for string literal"
				);
				throw ex;
			}
		}
		else
		{
			if(g.eof() || g.ch() == '\n')
			{
				ParsingErrorException ex(
					stream->cursorLocation(),
					"Unterminated string literal"
				);
				throw ex;
			}
		}

		char c = g.ch();

		switch(state)
		{
			case Initial:
			{
				if(c == '\'' || c == '\"')
				{
					stream->startParsing();
					quoteChar = c;
					state = Normal;
				}
				else if(isspace(c))
				{
					/* no nothing */
				}
				else
				{
					throw ParsingErrorException(stream->cursorLocation(), "Unexpected character found while looking for string literal");
				}
				break;
			}
			case Normal:
			{
				if(c == '\\')
				{
					escStart = stream->position();
					state = Slash;
				}
				else if(c == quoteChar)
				{
					state = Finished;
				}
				else
				{
					string_.push_back(c);
				}
				break;
			}
			case Slash:
			{
				if(c == 'x' || c == 'X')
				{
					base = 16;
					state = Hex;
				}
				else if(c >= '0' && c <='7')
				{
					base = 8;
					g.discard();
					state = Hex;
				}
				else
				{
					char const * pEsc = escapeTable;
					for(;;)
					{
						if(!*pEsc)
						{
							ParsingErrorException ex(
								stream->locationFrom(escStart),
								"Unrecognized escape sequence"
							);
							throw ex;
						}

						char c1 = *(pEsc++);
						char c2 = *(pEsc++);

						if(c == c1)
						{
							string_.push_back(c2);
							break;
						}
					}
					state = Normal;
				}
				break;
			}
			case Hex:
			{
				int val = digitValue(c, base);

				if(val >= 0)
				{
					digVal = digVal * base + val;
					if(digVal > 255)
					{
						ParsingErrorException ex(
							SourceLocation(stream->locationFrom(escStart)),
							"Value of the character code in escape sequence is out of range"
						);
						throw ex;
					}
				}
				else
				{
					g.discard();
					string_.push_back((char)digVal);
					digVal = 0; base = 0;
				}
				break;
			}
		}
	}

	return stream->location();
}


SourceLocation NameParser::parse(InputStream * stream)
{
	enum State { Initial, Normal, Finished };

	for(State state = Initial; state != Finished; )
	{
		InputGetter g(stream);

		char c = g.ch();

		if(state == Initial)
		{
			if(g.eof())
			{
				ParsingErrorException ex(
					stream->cursorLocation(),
					"Unexpected end of file while looking for identifier or keyword"
				);
				throw ex;
			}
			else if(isalpha(c) || c == '_')
			{
				stream->startParsing();
				name_.push_back(c);
				state = Normal;
			}
			else if(isspace(c))
			{
				/* no nothing */
			}
			else
			{
				ParsingErrorException ex(
					stream->cursorLocation(),
					"Unexpected character found while looking for identifier or keyword"
				);
				throw ex;
			}
		}
		else
		{
			if(isalpha(c) || isdigit(c) || c == '_')
			{
				name_.push_back(c);
			}
			else
			{
				g.discard();
				state = Finished;
			}
		}
	}

	return stream->location();
}

SourceLocation NumberParser::parse(InputStream * stream)
{
	//  [+-]?(0[xXcCbBdD])?[0-9a-fA-F]+
	//  Sign LeadingZero BasePrefix, Number
	
	enum State { Initial, Sign, LeadingZero, BasePrefix, Number, Finished };
	bool negative = false;
	int base = 10;

	static int const maxInt = (1 << (8*sizeof(int)-2)) + 1;
	
	int maxIntDiv = maxInt / base;
	int maxIntMod = maxInt % base;
	
	for(State state = Initial; state != Finished; )
	{
		InputGetter g(stream);
		char c = g.ch();
		int val = digitValue(c, base);

		switch(state)
		{
		/**********************************************************************************/
			case Initial:
			{
				if(g.eof())
				{
					ParsingErrorException ex(
						stream->cursorLocation(),
						"Unexpected end of file while looking for number"
					);
					throw ex;
				}
				else if(c == '+' || c == '-')
				{
					stream->startParsing();
					negative = (c == '-');
					if(negative)
					{
						//base prefix was not specified yet, default base is 10, maxInt would never end in '9';
						++maxIntMod;
					}
					state = Sign;
				}
				else if(c == '0')
				{
					stream->startParsing();
					state = LeadingZero;
				}
				else if(val >= 0)
				{
					stream->startParsing();
					intVal_ = val;
					state = Number;
				}
				else if(isspace(c))
				{
					/* do nothing */
				}
				else
				{
					ParsingErrorException ex(
						stream->cursorLocation(),
						"Unexpected character found while looking for number"
					);
					throw ex;
				}
				break;
			}
	/**********************************************************************************/
			case LeadingZero:
			{
				if(c == 'x' || c == 'X')
				{
					base = 16;
					state = BasePrefix;
				}
				else if(c == 'c' || c == 'C')
				{
					base = 8;
					state = BasePrefix;
				}
				else if(c == 'b' || c == 'B')
				{
					base = 2;
					state = BasePrefix;
				}
				else if(c == 'd' || c == 'D')
				{
					base = 10;
					state = BasePrefix;
				}
				else if(val >= 0)
				{
					intVal_ = val;
					state = Number;
				}
				else
				{
					g.discard();
					state = Finished;
				}

				if(state == BasePrefix)
				{
					maxIntMod = maxInt % base;
					maxIntDiv = maxInt / base;

					if(negative)
					{
						++maxIntMod;
						if(maxIntMod == base)
						{
							++maxIntDiv;
							maxIntMod = 0;
						}
					}
				}
				break;
			}
	/**********************************************************************************/
			case BasePrefix:
			{
				if(g.eof())
				{
					throw ParsingErrorException(stream->cursorLocation(), "Unexpected end of file after number base prefix");
				}
				else if(val >= 0)
				{
					intVal_ = val;
					state = Number;
				}
				else
				{
					throw ParsingErrorException(stream->cursorLocation(), "Invalid digit");
				}
				break;
			}
			case Number:
			{
				if(g.eof() || val < 0)
				{
					g.discard();
					state = Finished;
				}
				else
				{
					bool overflow = (intVal_ > maxIntDiv) || ( (intVal_ == maxIntDiv) && val > maxIntMod);
					if(overflow)
						throw ParsingErrorException(stream->location(), "Overflow in integral constant");
					intVal_ = intVal_ * base + val;
					state = Number;
				}
				break;
			}
	/**********************************************************************************/
		}	
	}

	if(negative)
	{
		intVal_ = -intVal_;
	}

	return stream->location();
}


SourceLocation OperatorParser::parse(InputStream * stream)
{
	/*
	(0) + (1) += (f)
	      (1) +` (f)
	(0) - (1) -= (f)
	      (1) -` (f)
	... * / % | ^ & $ ! =
	(0) : (2) := (f)
	      (2) :` (e)
	(0) ~ (f)
	(0)')'(f)
	(0)'('(f)
	(0)';'(f)
	(0) , (f)
	(0) > (3) >> (1) >>= (f)
	             (1) >>` (f)
		  (3) >= (f)
    (0) > (3) >> (1) >>= (f)
		         (1) >>` (f)
		  (3) >= (f)
*/
	enum State { Initial, State1, State2, State3, Finished, Failed };

	for(State state = Initial; state != Finished; )
	{
		if(state == Failed)
		{
			throw ParsingErrorException(stream->location(), "Unrecognized operator");
		}

		InputGetter g(stream);
		char c = g.ch();

		switch(state)
		{
		case Initial:
			stream->startParsing();
			switch(c)
			{
			case '+': case '-':
			case '*': case '/':
			case '%': case '^':
			case '|': case '&':
			case '!': case '$':
			case '=': 
				state = State1;
				break;
			case ':':
				state = State2;
				break;
			case '~':
			case '(':
			case ')':
			case ';':
			case ',':
				state = Finished;
				break;
			case '>': case '<':
				state = State3;
				break;
			default:
				g.discard();
				state = Failed;
				break;
			}
			break;
		case State1:
			if(g.eof() || c != '=')
				g.discard();
			state = Finished;
			break;
		case State2:
			if(g.eof() || c != '=')
			{
				g.discard();
				state = Failed;
			}
			else
			{
				state = Finished;
			}
			break;
		case State3:
			if(!g.eof() && c == string_.at(0))
				state = State1;
			else
			{
				if(g.eof() || c != '=')
					g.discard();
				state = Finished;
			}
			break;			
		}

		if(!g.isDiscarded())
		{
			string_.push_back(c);
		}
	}

	return stream->location();
}